package com.jwiki.app.rpcui.menu.setup;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.springframework.util.CollectionUtils;

import com.jwiki.app.rpcui.attribute.ApiJarAttribute;
import com.jwiki.app.rpcui.config.Configurations;
import com.jwiki.app.rpcui.util.ClassUtils;
import com.jwiki.app.rpcui.util.SwtUtils;
import com.jwiki.app.rpcui.util.XmlUtils;
import com.jwiki.app.rpcui.util.handler.XmlHandler;

public class ApiJarFileDialog extends Dialog {

    protected Object result;
    protected Shell shellApiJarFile;

    private Tree treeJarAttribute;
    private Button btnExpand;
    private Button btnCollapse;
    private Button btnRemove;

    private Document jarAttributeXmlDoc;
    private Label lblEmpty;

    /**
     * Create the dialog.
     * @param parent
     * @param style
     */
    public ApiJarFileDialog(Shell parent, int style) {
        super(parent, style | SwtUtils.DIALOG_RESIZE);
    }

    /**
     * Open the dialog.
     * @return the result
     * @throws Exception
     */
    public Object open() throws Exception {
        createContents();
        SwtUtils.centerDialog(shellApiJarFile, getParent());
        jarAttributeXmlDoc = parseJarAttributeDocument(treeJarAttribute, new File(Configurations.FILE_ATTRIBUTES));
        shellApiJarFile.open();
        shellApiJarFile.layout();
        Display display = getParent().getDisplay();
        while (!shellApiJarFile.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        return result;
    }

    /**
     * Create contents of the dialog.
     */
    private void createContents() {
        shellApiJarFile = new Shell(getParent(), getStyle());
        shellApiJarFile.setImages(Configurations.DEFAULT_ICON_IMAGES);
        shellApiJarFile.setSize(600, 365);
        shellApiJarFile.setText("Api Jar Files");
        shellApiJarFile.setLayout(new GridLayout(2, false));

        treeJarAttribute = new Tree(shellApiJarFile, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
        treeJarAttribute.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                btnRemove.setEnabled(false);
                for (TreeItem selectedTreeItem : treeJarAttribute.getSelection()) {
                    for (TreeItem treeItem : treeJarAttribute.getItems()) {
                        if (selectedTreeItem.equals(treeItem)) {
                            btnRemove.setEnabled(true);
                            return;
                        }
                    }
                }
            }
        });
        treeJarAttribute.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent arg0) {
                if (treeJarAttribute.getItemCount() > 0) {
                    btnExpand.setEnabled(true);
                    btnCollapse.setEnabled(true);
                }
            }
        });
        treeJarAttribute.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

        Composite composite = new Composite(shellApiJarFile, SWT.NONE);
        GridLayout gl_composite = new GridLayout(1, false);
        gl_composite.marginWidth = 0;
        gl_composite.marginHeight = 0;
        composite.setLayout(gl_composite);
        GridData gd_composite = new GridData(SWT.CENTER, SWT.FILL, false, true, 1, 1);
        gd_composite.widthHint = 80;
        composite.setLayoutData(gd_composite);

        Button btnAdd = new Button(composite, SWT.NONE);
        btnAdd.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        btnAdd.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                AddJarFileDialog dlgAddJarFile = new AddJarFileDialog(ApiJarFileDialog.this, shellApiJarFile,
                        SwtUtils.DIALOG_DEFAULT_STYLE);
                dlgAddJarFile.open();
            }
        });
        btnAdd.setBounds(0, 0, 80, 27);
        btnAdd.setText("Add...");

        btnExpand = new Button(composite, SWT.NONE);
        btnExpand.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        btnExpand.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem[] treeItems = treeJarAttribute.getSelection();
                if (treeItems == null || treeItems.length == 0) {
                    treeItems = treeJarAttribute.getItems();
                }
                SwtUtils.expandAllTreeItems(treeItems, true);
            }
        });
        btnExpand.setEnabled(false);
        btnExpand.setBounds(0, 66, 80, 27);
        btnExpand.setText("Expand");

        btnCollapse = new Button(composite, SWT.NONE);
        btnCollapse.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        btnCollapse.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem[] treeItems = treeJarAttribute.getSelection();
                if (treeItems == null || treeItems.length == 0) {
                    treeItems = treeJarAttribute.getItems();
                }
                SwtUtils.expandAllTreeItems(treeItems, false);
            }
        });
        btnCollapse.setEnabled(false);
        btnCollapse.setBounds(0, 99, 80, 27);
        btnCollapse.setText("Collapse");

        btnRemove = new Button(composite, SWT.NONE);
        btnRemove.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        btnRemove.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                boolean isOK = MessageDialog.openConfirm(shellApiJarFile, "Confirm Remove",
                        "Are you sure you want to remove selected nodes?");
                if (isOK) {
                    for (TreeItem treeItem : treeJarAttribute.getSelection()) {
                        treeItem.removeAll();
                        removeElementByJarName(jarAttributeXmlDoc, treeItem.getText());
                        treeItem.dispose();
                    }
                    btnRemove.setEnabled(false);
                }
            }
        });
        btnRemove.setEnabled(false);
        btnRemove.setBounds(0, 33, 80, 27);
        btnRemove.setText("Remove");

        lblEmpty = new Label(composite, SWT.NONE);
        lblEmpty.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true, 1, 1));

        Button btnSave = new Button(composite, SWT.NONE);
        btnSave.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        btnSave.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    try {
                        // remove unused api jars
                        Set<String> usedJarNames = new HashSet<String>();
                        List<?> selectedNodes = jarAttributeXmlDoc.selectNodes("//attribute/@name");
                        if (!CollectionUtils.isEmpty(selectedNodes)) {
                            for (int i = 0; i < selectedNodes.size(); i++) {
                                Attribute selectedNode = (Attribute) selectedNodes.get(i);
                                usedJarNames.add(selectedNode.getText());
                            }
                        }
                        Iterator<File> it = FileUtils.iterateFiles(new File(Configurations.DIR_API),
                                new String[] { "jar" }, false);
                        while (it.hasNext()) {
                            File apiJarFile = it.next();
                            if (!usedJarNames.contains(apiJarFile.getName())) {
                                FileUtils.forceDelete(apiJarFile);
                            }
                        }
                    } catch (Exception ex) {
                        ex.printStackTrace();
                        MessageDialog.openError(shellApiJarFile, "Error Information",
                                "Remove api jar file failed, maybe it's being used, please exit and try again!");
                        return;
                    }
                    XmlUtils.saveDocument(new File(Configurations.FILE_ATTRIBUTES), jarAttributeXmlDoc);
                    shellApiJarFile.close();
                    shellApiJarFile.dispose();
                    MessageDialog.openInformation(getParent(), "Information", "Save attributes of api jars success!");
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
        btnSave.setBounds(0, 4, 80, 27);
        btnSave.setText("Save");

        Button btnClose = new Button(composite, SWT.NONE);
        btnClose.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        btnClose.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                shellApiJarFile.close();
                shellApiJarFile.dispose();
            }
        });
        btnClose.setBounds(0, 37, 80, 27);
        btnClose.setText("Close");
    }

    public void importAndParseJarFile(ApiJarAttribute attribute) throws Exception {
        // copy jar file
        File targetJarFile = new File(Configurations.DIR_API, attribute.getName());
        FileUtils.copyFile(new File(attribute.getDirectory()), targetJarFile);
        // copy dependency files of jar
        String[] dependencies = attribute.getDependencies();
        if (dependencies != null && dependencies.length > 0) {
            for (String dependency : dependencies) {
                FileUtils.copyFile(new File(dependency), new File(Configurations.DIR_DEPENDENCY, FilenameUtils.getName(dependency)));
            }
        }
        // get all interfaces
        List<Class<?>> classList = ClassUtils.getAllInterfaces(targetJarFile);
        // create new attribute xml
        File targetXmlFile = new File(Configurations.DIR_TEMP, attribute.getName() + ".attr.xml");
        XmlUtils.createAndSave(targetXmlFile, new XmlHandler() {
            @Override
            public void handle(Document xmlDoc) throws Exception {
                // attributes
                Element rootEl = DocumentHelper.createElement("attributes");
                xmlDoc.setRootElement(rootEl);
                // attribute
                Element attrEl = rootEl.addElement("attribute");
                attrEl.addAttribute("name", attribute.getName());
                // groupId
                attrEl.addElement("groupId").setText(attribute.getGroupId());
                // artifactId
                attrEl.addElement("artifactId").setText(attribute.getArtifactId());
                // version
                attrEl.addElement("version").setText(attribute.getVersion());
                // interface
                if (!CollectionUtils.isEmpty(classList)) {
                    // interfaces
                    Element interfacesEl = attrEl.addElement("interfaces");
                    for (Class<?> clazz : classList) {
                        // interface
                        Element interfaceEl = interfacesEl.addElement("interface");
                        interfaceEl.addAttribute("name", clazz.getName());

                        Method[] methods = clazz.getMethods();
                        if (methods != null && methods.length > 0) {
                            // methods
                            Element methodsEl = interfaceEl.addElement("methods");
                            for (Method method : methods) {
                                // method
                                Element methodEl = methodsEl.addElement("method");
                                methodEl.addAttribute("name", method.getName());

                                Type[] parameterTypes = method.getGenericParameterTypes();
                                if (parameterTypes != null && parameterTypes.length > 0) {
                                    // arguments
                                    Element parameterTypesEl = methodEl.addElement("arguments");
                                    for (int i = 0; i < parameterTypes.length; i++) {
                                        // argument
                                        Element parameterTypeEl = parameterTypesEl.addElement("arg" + i);
                                        parameterTypeEl.addAttribute("type", ClassUtils.toDisplayClassName(ClassUtils
                                                .getActualTypeClassName(parameterTypes[i])));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        });
        // remove old interfaces
        for (TreeItem treeItem : treeJarAttribute.getItems()) {
            if (treeItem.getText().contains(attribute.getArtifactId())) {
                treeItem.removeAll();
                treeItem.dispose();
            }
        }
        // show all interfaces
        Document newXmlDoc = parseJarAttributeDocument(treeJarAttribute, targetXmlFile);
        // remove jar attribute
        removeElementByJarName(jarAttributeXmlDoc, attribute.getArtifactId());
        // add jar attribute
        String jarAttrXml = newXmlDoc.getRootElement().element("attribute").asXML();
        jarAttributeXmlDoc.getRootElement().add(DocumentHelper.parseText(jarAttrXml).getRootElement());
        FileUtils.forceDelete(targetXmlFile);
    }

    public Document parseJarAttributeDocument(Tree tree, File attrXml) throws Exception {
        Document xmlDoc = XmlUtils.getDocument(attrXml);
        List<?> attrEls = xmlDoc.selectNodes("//attribute");
        if (!CollectionUtils.isEmpty(attrEls)) {
            for (int i = 0; i < attrEls.size(); i++) {
                Element attrEl = (Element) attrEls.get(i);
                TreeItem jarItem = new TreeItem(tree, SWT.NONE);
                // api jar
                jarItem.setText(attrEl.attributeValue("name"));
                // interface
                List<?> interfaceEls = attrEl.selectNodes("interfaces/interface");
                if (!CollectionUtils.isEmpty(interfaceEls)) {
                    for (int j = 0; j < interfaceEls.size(); j++) {
                        Element interfaceEl = (Element) interfaceEls.get(j);
                        TreeItem interfaceItem = new TreeItem(jarItem, SWT.NONE);
                        interfaceItem.setText(interfaceEl.attributeValue("name"));
                        // method
                        List<?> methodEls = interfaceEl.selectNodes("methods/method");
                        if (!CollectionUtils.isEmpty(methodEls)) {
                            for (int m = 0; m < methodEls.size(); m++) {
                                Element methodEl = (Element) methodEls.get(m);
                                TreeItem methodItem = new TreeItem(interfaceItem, SWT.NONE);
                                // argument
                                Element argumentsEl = methodEl.element("arguments");
                                if (argumentsEl == null) {
                                    methodItem.setText(methodEl.attributeValue("name") + "()");
                                } else {
                                    List<?> argumentEls = argumentsEl.elements();
                                    String[] parameterTypes = new String[argumentEls.size()];
                                    for (int n = 0; n < argumentEls.size(); n++) {
                                        parameterTypes[n] = ((Element) argumentEls.get(n)).attributeValue("type");
                                    }
                                    methodItem.setText(methodEl.attributeValue("name") + "(" + StringUtils.join(parameterTypes,
                                            ", ") + ")");
                                }
                            }
                        }
                    }
                }
            }
        }

        return xmlDoc;
    }

    private void removeElementByJarName(Document xmlDoc, String jarName) {
        List<?> selectedNodes = xmlDoc.selectNodes("//attribute[contains(@name, \"" + jarName + "\")]");
        if (!CollectionUtils.isEmpty(selectedNodes)) {
            Element rootEl = xmlDoc.getRootElement();
            for (int i = 0; i < selectedNodes.size(); i++) {
                Element selectedNode = (Element) selectedNodes.get(i);
                rootEl.remove(selectedNode);
            }
        }
    }

}
